﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using JavaSubstitutes;
using Sonar;
using Level;

namespace Sprites
{
	public class MapScene : Scene
	{
        //private static readonly int TileType.TILE_GRASS = 0;
        //private static readonly int TileType.TILE_WATER = 1;
        //private static readonly int TileType.TILE_LEVEL = 2;
        //private static readonly int TileType.TILE_ROAD = 3;
        //private static readonly int TileType.TILE_DECORATION = 4;
        public enum TileType {TILE_GRASS = 0, TILE_WATER = 1, TILE_LEVEL = 2, TILE_ROAD = 3, TILE_DECORATION = 4};        

		//private int[,] level;

        private TileType[,] level;
		private int[,] data;

		private int xMario, yMario;
		private int xMarioA, yMarioA;

		private int tickCount;
		private GraphicsConfiguration graphicsConfiguration;
		private WriteableBitmap staticBg;
		
		//private Graphics staticGr;
        private WriteableBitmap staticGr;

		private int moveTime = 0;
		private MarioComponent marioComponent;
		private int seed;
		private int worldNumber;
        private bool canEnterLevel = false;

		private int levelId = 0;
		private int farthest = 0;
		private int xFarthestCap = 0;
		private int yFarthestCap = 0;

        private DecimalFormat df = new DecimalFormat("00");
		
		public MapScene(GraphicsConfiguration graphicsConfiguration, MarioComponent marioComponent, int seed)
		{
			this.graphicsConfiguration = graphicsConfiguration;
			this.marioComponent = marioComponent;
			this.seed = seed;

			staticBg = new WriteableBitmap(320, 240); //todo: make the screen size configurable
            staticGr = new WriteableBitmap(320, 240); 
		}

		public override void init()
		{
			worldNumber = -1;
			nextWorld();
		}

		private void nextWorld()
		{
			worldNumber++;

			if (worldNumber == 8)
			{
				marioComponent.ShowWinScene();
				return;
			}

            Reset();

            while (!generateLevel());
			renderStatic(staticGr);
            staticGr.Invalidate();
		}

        private void Reset()
        {
            moveTime = 0;
            levelId = 0;
            farthest = 0;
            xFarthestCap = 0;
            yFarthestCap = 0;

            seed = RandomExtensions.nextLong();
        }

		public void startMusic()
		{
			Art.startMusic(0);
		}

		private bool generateLevel()
		{
			ImprovedNoise n0 = new ImprovedNoise(RandomExtensions.nextLong());
			ImprovedNoise n1 = new ImprovedNoise(RandomExtensions.nextLong());
			ImprovedNoise dec = new ImprovedNoise(RandomExtensions.nextLong());

			int width = 320 / 16 + 1; //meliot: background tiles are 16x16
			int height = 240 / 16 + 1;
			//level = new int[width, height];
            level = new TileType[width, height];            
			data = new int[width, height];
			double xo0 = RandomExtensions.nextDouble() * 512;
			double yo0 = RandomExtensions.nextDouble() * 512;
			double xo1 = RandomExtensions.nextDouble() * 512;
			double yo1 = RandomExtensions.nextDouble() * 512;
			for (int x = 0; x < width; x++)
			{
				for (int y = 0; y < height; y++)
				{
					double xd = ((x + 1) / (double)width - 0.5) * 2;
					double yd = ((y + 1) / (double)height - 0.5) * 2;
					double d = Math.Sqrt(xd * xd + yd * yd) * 2;
					if (x == 0 || y == 0 || x >= width - 3 || y >= height - 3) d = 100;
					double t0 = n0.perlinNoise(x * 10.0 + xo0, y * 10.0 + yo0);
					double t1 = n1.perlinNoise(x * 10.0 + xo1, y * 10.0 + yo1);
					double td = (t0 - t1);
					double t = (td * 2);
					level[x, y] = t > 0 ? TileType.TILE_WATER : TileType.TILE_GRASS;
				}
			}

			int lowestX = 9999;
			int lowestY = 9999;
			int t2 = 0;
			for (int i = 0; i < 100 && t2 < 12; i++)
			{
				int x = RandomExtensions.nextInt((width - 1) / 3) * 3 + 2;
				int y = RandomExtensions.nextInt((height - 1) / 3) * 3 + 1;
				if (level[x, y] == TileType.TILE_GRASS)
				{
					if (x < lowestX)
					{
						lowestX = x;
						lowestY = y;
					}
					level[x, y] = TileType.TILE_LEVEL;
					data[x, y] = -1;
					t2++;
				}
			}

			data[lowestX, lowestY] = -2;

			while (findConnection(width, height))
				;

			findCaps(width, height);

			if (xFarthestCap == 0) return false;

			data[xFarthestCap, yFarthestCap] = -2;
			data[xMario / 16, yMario / 16] = -11;


			for (int x = 0; x < width; x++)
			{
				for (int y = 0; y < height; y++)
				{
					if (level[x, y] == TileType.TILE_GRASS && (x != xFarthestCap || y != yFarthestCap - 1))
					{
						double t0 = dec.perlinNoise(x * 10.0 + xo0, y * 10.0 + yo0);
						if (t0 > 0) level[x, y] = TileType.TILE_DECORATION;
					}
				}
			}

			return true;
		}

		private void travel(int x, int y, int dir, int depth)
		{
			if (level[x, y] != TileType.TILE_ROAD && level[x, y] != TileType.TILE_LEVEL)
			{
				return;
			}
			if (level[x, y] == TileType.TILE_ROAD)
			{
				if (data[x, y] == 1) return;
				else data[x, y] = 1;
			}

			if (level[x, y] == TileType.TILE_LEVEL)
			{
				if (data[x, y] > 0)
				{
					if (levelId != 0 && RandomExtensions.nextInt(4) == 0)
					{
						data[x, y] = -3;
					}
					else
					{
						data[x, y] = ++levelId;
					}
				}
				else if (depth > 0)
				{
					data[x, y] = -1;
					if (depth > farthest)
					{
						farthest = depth;
						xFarthestCap = x;
						yFarthestCap = y;
					}
				}
			}

			if (dir != 2) travel(x - 1, y, 0, depth++);
			if (dir != 3) travel(x, y - 1, 1, depth++);
			if (dir != 0) travel(x + 1, y, 2, depth++);
			if (dir != 1) travel(x, y + 1, 3, depth++);
		}

		private void findCaps(int width, int height)
		{
			int xCap = -1;
			int yCap = -1;

			for (int x = 0; x < width; x++)
			{
				for (int y = 0; y < height; y++)
				{
					if (level[x, y] == TileType.TILE_LEVEL)
					{
						int roads = 0;
						for (int xx = x - 1; xx <= x + 1; xx++)
							for (int yy = y - 1; yy <= y + 1; yy++)
							{
								if (level[xx, yy] == TileType.TILE_ROAD) roads++;
							}

						if (roads == 1)
						{
							if (xCap == -1)
							{
								xCap = x;
								yCap = y;
							}
							data[x, y] = 0;
						}
						else
						{
							data[x, y] = 1;
						}
					}
				}
			}

			xMario = xCap * 16;
			yMario = yCap * 16;

			travel(xCap, yCap, -1, 0);
		}

		private bool findConnection(int width, int height)
		{
			for (int x = 0; x < width; x++)
			{
				for (int y = 0; y < height; y++)
				{
					if (level[x, y] == TileType.TILE_LEVEL && data[x, y] == -1)
					{
						connect(x, y, width, height);
						return true;
					}
				}
			}
			return false;
		}

		private void connect(int xSource, int ySource, int width, int height)
		{
			int maxDist = 10000;
			int xTarget = 0;
			int yTarget = 0;
			for (int x = 0; x < width; x++)
			{
				for (int y = 0; y < height; y++)
				{
					if (level[x, y] == TileType.TILE_LEVEL && data[x, y] == -2)
					{
						int xd = Math.Abs(xSource - x);
						int yd = Math.Abs(ySource - y);
						int d = xd * xd + yd * yd;
						if (d < maxDist)
						{
							xTarget = x;
							yTarget = y;
							maxDist = d;
						}
					}
				}
			}

			MakeRoad(xSource, ySource, xTarget, yTarget);
			level[xSource, ySource] = TileType.TILE_LEVEL;
			data[xSource, ySource] = -2;
			return;
		}

		private void MakeRoad(int x0, int y0, int x1, int y1)
		{
			bool xFirst = RandomExtensions.nextBoolean();

			if (xFirst)
			{
				while (x0 > x1)
				{
					data[x0, y0] = 0;
					level[x0--, y0] = TileType.TILE_ROAD;
				}
				while (x0 < x1)
				{
					data[x0, y0] = 0;
					level[x0++, y0] = TileType.TILE_ROAD;
				}
			}
			while (y0 > y1)
			{
				data[x0, y0] = 0;
				level[x0, y0--] = TileType.TILE_ROAD;
			}
			while (y0 < y1)
			{
				data[x0, y0] = 0;
				level[x0, y0++] = TileType.TILE_ROAD;
			}
			if (!xFirst)
			{
				while (x0 > x1)
				{
					data[x0, y0] = 0;
					level[x0--, y0] = TileType.TILE_ROAD;
				}
				while (x0 < x1)
				{
					data[x0, y0] = 0;
					level[x0++, y0] = TileType.TILE_ROAD;
				}
			}
		}

        public void renderStatic(WriteableBitmap wbmp)
		{
			//todo
			/* 
			 * the concept here is that we render the static stuff ONCE for this level, then we never have to do it again
			 * but that's not work since the NON static stuff is overwriting it, so we're going to render both parts until we get
			 * the other stuff working and then come back to this, though the performance may not be an issue either way
			 */

			for (int x = 0; x < 320 / 16; x++)
			{
				for (int y = 0; y < 240 / 16; y++)
				{
					wbmp.drawImage(Art.map[worldNumber / 4, 0], x * 16, y * 16);
					if (level[x, y] == TileType.TILE_LEVEL)
					{
						int type = data[x, y];
						if (type == 0) //0== road?
						{
							wbmp.drawImage(Art.map[0, 7], x * 16, y * 16);
						}
						else if (type == -1)
						{
							wbmp.drawImage(Art.map[3, 8], x * 16, y * 16);
						}
						else if (type == -3)
						{
							wbmp.drawImage(Art.map[0, 8], x * 16, y * 16);
						}
						else if (type == -10)
						{
							wbmp.drawImage(Art.map[1, 8], x * 16, y * 16);
						}
						else if (type == -11)
						{
							wbmp.drawImage(Art.map[1, 7], x * 16, y * 16);
						}
						else if (type == -2)
						{
							wbmp.drawImage(Art.map[2, 7], x * 16, y * 16 - 16);
							wbmp.drawImage(Art.map[2, 8], x * 16, y * 16);
						}
						else
						{
							wbmp.drawImage(Art.map[type - 1, 6], x * 16, y * 16);
						}
					}
					else if (level[x, y] == TileType.TILE_ROAD)
					{
						int p0 = isRoad(x - 1, y) ? 1 : 0;
						int p1 = isRoad(x, y - 1) ? 1 : 0;
						int p2 = isRoad(x + 1, y) ? 1 : 0;
						int p3 = isRoad(x, y + 1) ? 1 : 0;
						int s = p0 + p1 * 2 + p2 * 4 + p3 * 8;                        
						wbmp.drawImage(Art.map[s, 2], x * 16, y * 16);                        
					}
					else if (level[x, y] == TileType.TILE_WATER)
					{
						for (int xx = 0; xx < 2; xx++)
						{
							for (int yy = 0; yy < 2; yy++)
							{
								int p0 = isWater(x * 2 + (xx - 1), y * 2 + (yy - 1)) ? 0 : 1;
								int p1 = isWater(x * 2 + (xx + 0), y * 2 + (yy - 1)) ? 0 : 1;
								int p2 = isWater(x * 2 + (xx - 1), y * 2 + (yy + 0)) ? 0 : 1;
								int p3 = isWater(x * 2 + (xx + 0), y * 2 + (yy + 0)) ? 0 : 1;
								int s = p0 + p1 * 2 + p2 * 4 + p3 * 8 - 1;
								if (s >= 0 && s < 14)
								{
									wbmp.drawImage(Art.map[s, 4 + ((xx + yy) & 1)], x * 16 + xx * 8, y * 16 + yy * 8);
								}
							}
						}
					}
				}
			}
            wbmp.Invalidate();
		}

		
		public override void render(WriteableBitmap wbmp, double alpha)
		{
			renderStatic(wbmp);

			for (int y = 0; y <= 240 / 16; y++)
			{
				for (int x = 320 / 16; x >= 0; x--)
				{
					if (level[x, y] == TileType.TILE_WATER)
					{
						if (isWater(x * 2 - 1, y * 2 - 1))
						{
							wbmp.drawImage(Art.map[15, 4 + (tickCount / 6 + y) % 4], x * 16 - 8, y * 16 - 8);
						}
					}
					else if (level[x, y] == TileType.TILE_DECORATION)
					{
						wbmp.drawImage(Art.map[(tickCount + y * 12) / 6 % 4, 10 + worldNumber % 4], x * 16, y * 16);
					}
					else if (level[x, y] == TileType.TILE_LEVEL && data[x, y] == -2 && tickCount / 12 % 2 == 0)
					{
						wbmp.drawImage(Art.map[3, 7], x * 16 + 16, y * 16 - 16);
					}
				}
			}
			if (!Mario.large)
			{
				wbmp.drawImage(Art.map[(tickCount) / 6 % 2, 1], xMario + (int)(xMarioA * alpha), yMario + (int)(yMarioA * alpha) - 6);
			}
			else
			{
				if (!Mario.fire)
				{
					wbmp.drawImage(Art.map[(tickCount) / 6 % 2 + 2, 0], xMario + (int)(xMarioA * alpha), yMario + (int)(yMarioA * alpha) - 6 - 16);
					wbmp.drawImage(Art.map[(tickCount) / 6 % 2 + 2, 1], xMario + (int)(xMarioA * alpha), yMario + (int)(yMarioA * alpha) - 6);
				}
				else
				{
					wbmp.drawImage(Art.map[(tickCount) / 6 % 2 + 4, 0], xMario + (int)(xMarioA * alpha), yMario + (int)(yMarioA * alpha) - 6 - 16);
					wbmp.drawImage(Art.map[(tickCount) / 6 % 2 + 4, 1], xMario + (int)(xMarioA * alpha), yMario + (int)(yMarioA * alpha) - 6);
				}
			}

			drawStringDropShadow(wbmp, "MARIO " + df.format(Mario.lives), 0, 0, 7);
			drawStringDropShadow(wbmp, "WORLD " + (worldNumber + 1), 32, 0, 7);

            wbmp.Invalidate();
		}

        //todo: move to art
		private void drawStringDropShadow(WriteableBitmap wbmp, String text, int x, int y, int c)
		{
			drawString(wbmp, text, x * 8 + 5, y * 8 + 5, 0);
			drawString(wbmp, text, x * 8 + 4, y * 8 + 4, c);
		}

        //todo: move to art
		private void drawString(WriteableBitmap wbmp, String text, int x, int y, int c)
		{
			char[] ch = text.toCharArray();
			for (int i = 0; i < ch.Length; i++)
			{
				wbmp.drawImage(Art.font[ch[i] - 32, c], x + i * 8, y);
			}
		}

		private bool isRoad(int x, int y)
		{
			if (x < 0) x = 0;
			if (y < 0) y = 0;
			if (level[x, y] == TileType.TILE_ROAD) return true;
			if (level[x, y] == TileType.TILE_LEVEL) return true;
			return false;
		}

		private bool isWater(int x, int y)
		{
			if (x < 0) x = 0;
			if (y < 0) y = 0;

			for (int xx = 0; xx < 2; xx++)
			{
				for (int yy = 0; yy < 2; yy++)
				{
					if (level[(x + xx) / 2, (y + yy) / 2] != TileType.TILE_WATER) return false;
				}
			}

			return true;
		}
	
		public override void tick()
		{
			xMario += xMarioA;
			yMario += yMarioA;
			tickCount++;
			int x = xMario / 16;
			int y = yMario / 16;
			if (level[x, y] == TileType.TILE_ROAD)
			{
				data[x, y] = 0;
			}

			if (moveTime > 0)
			{
				moveTime--;
			}
			else
			{
				xMarioA = 0;
				yMarioA = 0;
				if (canEnterLevel && keys[Mario.KEY_JUMP])
				{
					if (level[x, y] == TileType.TILE_LEVEL && data[x, y] == -11)
					{
                        //meliot: ?
					}
					else
					{
						if (level[x, y] == TileType.TILE_LEVEL && data[x, y] != 0 && data[x, y] > -10)
						{
							Mario.levelString = (worldNumber + 1) + "-";
							int difficulty = worldNumber + 1;
							int type = LevelGenerator.TYPE_OVERGROUND;
							//if (data[x, y] > 1 && new Random(seed + x * 313211 + y * 534321).nextInt(3) == 0) //original- wtf on the seed?
							if (data[x, y] > 1 && RandomExtensions.nextInt(3) == 0)
							{
								type = LevelGenerator.TYPE_UNDERGROUND;
							}
							if (data[x, y] < 0)
							{
								if (data[x, y] == -2)
								{
									Mario.levelString += "X";
									difficulty += 2;
								}
								else if (data[x, y] == -1)
								{
									Mario.levelString += "?";
								}
								else
								{
									Mario.levelString += "#";
									difficulty += 1;
								}

								type = LevelGenerator.TYPE_CASTLE;
							}
							else
							{
								Mario.levelString += data[x, y];
							}

							Art.stopMusic();
							//marioComponent.startLevel(seed * x * y + x * 31871 + y * 21871, difficulty, type);
                            //TODO: meliot- why's he using such an odd seed?
                            marioComponent.startLevel(RandomExtensions.nextInt(), difficulty, type);

						}
					}
				}
				canEnterLevel = !keys[Mario.KEY_JUMP];

				if (keys[Mario.KEY_LEFT])
				{
					//keys[Mario.KEY_LEFT] = false;
                    Console.WriteLine("left");
					tryWalking(-1, 0);
				}
				if (keys[Mario.KEY_RIGHT])
				{
					//keys[Mario.KEY_RIGHT] = false;
                    Console.WriteLine("right");
					tryWalking(1, 0);
				}
				if (keys[Mario.KEY_UP])
				{
					//keys[Mario.KEY_UP] = false;
                    Console.WriteLine("up");
					tryWalking(0, -1);
				}
				if (keys[Mario.KEY_DOWN])
				{
					//keys[Mario.KEY_DOWN] = false;
                    Console.WriteLine("down");
					tryWalking(0, 1);
				}
			}
		}

		public void tryWalking(int xd, int yd)
		{
			int x = xMario / 16;
			int y = yMario / 16;
			int xt = xMario / 16 + xd;
			int yt = yMario / 16 + yd;

			if (level[xt, yt] == TileType.TILE_ROAD || level[xt, yt] == TileType.TILE_LEVEL)
			{
				if (level[xt, yt] == TileType.TILE_ROAD)
				{
					if ((data[xt, yt] != 0) && (data[x, y] != 0 && data[x, y] > -10)) return;
				}
				xMarioA = xd * 8;
				yMarioA = yd * 8;
				moveTime = calcDistance(x, y, xd, yd) * 2 + 1;
			}
		}

		private int calcDistance(int x, int y, int xa, int ya)
		{
			int distance = 0;
			while (true)
			{
				x += xa;
				y += ya;
				if (level[x, y] != TileType.TILE_ROAD) return distance;
				if (level[x - ya, y + xa] == TileType.TILE_ROAD) return distance;
				if (level[x + ya, y - xa] == TileType.TILE_ROAD) return distance;
				distance++;
			}
		}

		public double getX(double alpha)
		{
			return 160; //meliot: ?
		}

		public double getY(double alpha)
		{
            return 120; //meliot: ?
		}

		public void levelWon()
		{
			int x = xMario / 16;
			int y = yMario / 16;
			if (data[x, y] == -2)
			{
				nextWorld();
				return;
			}
			if (data[x, y] != -3) data[x, y] = 0;
			else data[x, y] = -10;
			renderStatic(staticGr);
            staticGr.Invalidate();
		}
	}
}